/*
* L4D Health Bonus Exploit Blocker
* --------------------------------
*          by Downtown1
* --------------------------------
* 
* DESCRIPTION:
* >>>>>>>>>>>>
* 
* This plugin will block the exploits related to the health bonus:
* 
* Namely 
* 1) Passing pills around before closing the safe room door
* 2) Healing others with low health and then shooting them
* 
* In situation #1 the game thinks everyone has pills.
* In situation #2 the game thinks the healer still has a medkit so it's the same thing as getting
* 	2 extra medkits at the end of a level.
* 
* --------------------------------
* 
* THANKS TO
* >>>>>>>>>
* 
* Fission for helping me test this throughout the entire lifespan and helping me figure out
* how to reproduce the two health bonus exploits.
* 
* Trinity Gaming for doing a "live" 4 survivors test without using cheats or any SM commands.
* 
* ShadoMagi for inspiring me on using the side-effects of m_healthBuffer to update the health bonus.
*/

#pragma semicolon 1

#include <sourcemod>
#include <sdktools>

#define EBLOCK_DEBUG 0

#define EBLOCK_BONUS_UPDATE_DELAY 0.01

#define L4D_MAXCLIENTS_PLUS1 15

#define EBLOCK_VERSION "0.1.2"

#if EBLOCK_DEBUG
#define EBLOCK_BONUS_HEALTH_BUFFER 10.0
#else
#define EBLOCK_BONUS_HEALTH_BUFFER 1.0
#endif

#define EBLOCK_USE_DELAYED_UPDATES 0

new bool:painPillHolders[L4D_MAXCLIENTS_PLUS1];

public Plugin:myinfo = 
{
	name = "L4D Health Bonus Exploit Blocker",
	author = "Downtown1",
	description = "Block healing/pain killer related health bonus exploit",
	version = EBLOCK_VERSION,
	url = "https://forums.alliedmods.net/showthread.php?t=86897"
}

public OnPluginStart()
{
	// Add your own code here...
	CreateConVar("l4d_eb_health_bonus", EBLOCK_VERSION, "Version of the Health Bonus Exploit Blocker", FCVAR_PLUGIN|FCVAR_SPONLY|FCVAR_NOTIFY|FCVAR_REPLICATED);
	
	HookEvent("item_pickup", Event_ItemPickup);	
	HookEvent("pills_used", Event_PillsUsed);
	HookEvent("heal_success", Event_HealSuccess);
	
	HookEvent("round_start", Event_RoundStart);
	
	#if EBLOCK_DEBUG
	RegConsoleCmd("sm_updatehealth", Command_UpdateHealth);
	
	//RegConsoleCmd("sm_givehealth", Command_GiveHealth);
	#endif
}

public Action:Command_UpdateHealth(client, args)
{
	DelayedUpdateHealthBonus();
	
	return Plugin_Handled;
}


public Action:Event_ItemPickup(Handle:event, const String:name[], bool:dontBroadcast)
{	
	new player = GetClientOfUserId(GetEventInt(event, "userid"));
	
	new String:item[128];
	GetEventString(event, "item", item, sizeof(item));
	
	#if EBLOCK_DEBUG
	new String:curname[128];
	GetClientName(player,curname,128);
	
	if(strcmp(item, "pain_pills") == 0)		
		DebugPrintToAll("EVENT - Item %s picked up by %s [%d]", item, curname, player);
	#endif
	
	if(strcmp(item, "pain_pills") == 0)
	{
		painPillHolders[player] = true;
		DelayedPillUpdate();
	}
	
	return Plugin_Handled;
}

public Action:Event_PillsUsed(Handle:event, const String:name[], bool:dontBroadcast)
{	
	new player = GetClientOfUserId(GetEventInt(event, "userid"));
	
	#if EBLOCK_DEBUG
	new subject = GetClientOfUserId(GetEventInt(event, "subject"));
	
	new String:curname[128];
	GetClientName(player,curname,128);
	
	new String:curname_subject[128];
	GetClientName(subject,curname_subject,128);
	
	DebugPrintToAll("EVENT - %s [%d] used pills on subject %s [%d]", curname, player, curname_subject, subject);
	#endif
	
	painPillHolders[player] = false;
	
	return Plugin_Handled;
}



public Action:Event_HealSuccess(Handle:event, const String:name[], bool:dontBroadcast)
{	
	#if EBLOCK_DEBUG
	new player = GetClientOfUserId(GetEventInt(event, "userid"));
	new subject = GetClientOfUserId(GetEventInt(event, "subject"));
	
	new String:curname[128];
	GetClientName(player,curname,128);
	
	new String:curname_subject[128];
	GetClientName(subject,curname_subject,128);
	
	DebugPrintToAll("EVENT - %s [%d] healed %s [%d] successfully", curname, player, curname_subject, subject);
	#endif
	
	//UpdateHealthBonusForClient(player);
	//UpdateHealthBonusForClient(subject);
	DelayedUpdateHealthBonus();
	
	return Plugin_Handled;
}

public Action:Event_RoundStart(Handle:event, const String:name[], bool:dontBroadcast)
{	
	decl i;
	for (i = 1; i < L4D_MAXCLIENTS_PLUS1; i++)
	{
		painPillHolders[i] = false;
	}
	
	return Plugin_Handled;
}


DelayedUpdateHealthBonus()
{
	#if EBLOCK_USE_DELAYED_UPDATES
	CreateTimer(EBLOCK_BONUS_UPDATE_DELAY, Timer_DoUpdateHealthBonus, _, _);
	#else
	UpdateHealthBonus();
	#endif
	
	DebugPrintToAll("Delayed health bonus update");
}

public Action:Timer_DoUpdateHealthBonus(Handle:timer)
{
	UpdateHealthBonus();
}

UpdateHealthBonus()
{
	decl i;
	for (i = 1; i < L4D_MAXCLIENTS_PLUS1; i++)
	{
		if (IsClientInGame(i) && GetClientTeam(i) == 2) 
		{
			UpdateHealthBonusForClient(i);
		}
	}
}

DelayedPillUpdate()
{
	#if EBLOCK_USE_DELAYED_UPDATES
	CreateTimer(EBLOCK_BONUS_UPDATE_DELAY, Timer_PillUpdate, _, _);
	#else
	UpdateHealthBonusForPillHolders();
	#endif
	
	DebugPrintToAll("Delayed pill bonus update");
}

public Action:Timer_PillUpdate(Handle:timer)
{
	UpdateHealthBonusForPillHolders();
}

UpdateHealthBonusForPillHolders()
{
	decl i;
	for (i = 1; i < L4D_MAXCLIENTS_PLUS1; i++)
	{
		if (IsClientInGame(i) && GetClientTeam(i) == 2 && painPillHolders[i]) 
		{
			UpdateHealthBonusForClient(i);
		}
	}
}

UpdateHealthBonusForClient(client)
{
	//SendFakePlayerHurt(i);
	//SendFakeFriendlyFire(i);
	//SendFakePlayerHurtConcise(i);
	
	SendHurtMe(client);
}

SendHurtMe(i)
{
	//originally tried to send "hurtme" 
	//but this makes the player scream and also
	//the cheat command doesn't seem to work even in sv_cheats 0
	// even with the cheat flag stripped
	/*	SetEntityHealth(i, GetClientHealth(i)+1);
	
	// enable the hurtme command without sv_cheats
	new String:command[] = "hurtme";
	new flags = GetCommandFlags(command);
	
	SetCommandFlags(command, flags & ~FCVAR_CHEAT);
	
	FakeClientCommand(i, "hurtme 1");
	
	// restore z_spawn
	SetCommandFlags(command, flags);
	
	*/
	
	/*
	* when a person uses pills the m_healthBuffer gets set to 
	* minimum(50, 100-currentHealth)
	* 
	* it stays at that value until the person heals (or uses pills?)
	* or the round is over
	* 
	* once the m_healthBuffer property is non-0 the health bonus for that player
	* seems to keep updating
	* 
	* The first time we set it ourselves that player gets that much temp hp,
	* setting it afterwards crashes the server, and setting it after we set it
	* for the first time doesn't do anything.
	*/
	new Float:healthBuffer = GetEntPropFloat(i, Prop_Send, "m_healthBuffer");
	
	DebugPrintToAll("Health buffer for player [%d] is %f", i, healthBuffer);	
	if(healthBuffer == 0.0)
	{
		SetEntPropFloat(i, Prop_Send, "m_healthBuffer", EBLOCK_BONUS_HEALTH_BUFFER);
		DebugPrintToAll("Health buffer for player [%d] set to %f", i, EBLOCK_BONUS_HEALTH_BUFFER);
	}
	
	DebugPrintToAll("Sent hurtme to [%d]", i);
}

//sending the fake events does jack shit to actually update the health bonus
#if 0
SendFakeFriendlyFire(i)
{
	new Handle:event = CreateEvent("friendly_fire");	
	if(event == INVALID_HANDLE) 
	{
		DebugPrintToAll("Could not create event friendly_fire");
		return;
	}
	
	SetEventInt(event, "attacker", GetClientUserId(i));
	SetEventInt(event, "victim", GetClientUserId(i));
	SetEventInt(event, "guilty", 0);
	SetEventInt(event, "type", 0);
	
	FireEvent(event);
	
	DebugPrintToAll("Sent fake friendly_fire to [%d]", i);
}

SendFakePlayerHurt(i)
{
	new Handle:event = CreateEvent("player_hurt");	
	if(event == INVALID_HANDLE) 
	{
		DebugPrintToAll("Could not create event player_hurt");
		return;
	}
	
	SetEventInt(event, "userid", GetClientUserId(i));
	SetEventInt(event, "attacker", GetClientUserId(i));
	SetEventInt(event, "attackerentid", i);
	SetEventInt(event, "health", GetClientHealth(i)-1);
	
	SetEventInt(event, "armor", 0);
	SetEventString(event, "weapon", "pistol");
	SetEventInt(event, "dmg_health", 1);
	SetEventInt(event, "dmg_armor", 0);
	
	SetEventInt(event, "hitgroup", 0);
	SetEventInt(event, "type", 0);
	
	FireEvent(event);
	
	DebugPrintToAll("Sent fake player_hurt to [%d]", i);
}

SendFakePlayerHurtConcise(i)
{
	new Handle:event = CreateEvent("player_hurt_concise");	
	if(event == INVALID_HANDLE) 
	{
		DebugPrintToAll("Could not create event player_hurt_concise");
		return;
	}
	
	SetEventInt(event, "userid", GetClientUserId(i));
	SetEventInt(event, "attackerentid", i);
	
	SetEventInt(event, "dmg_health", 0);
	
	FireEvent(event);
	
	DebugPrintToAll("Sent fake player_hurt_concise to [%d]", i);
}
#endif

DebugPrintToAll(const String:format[], any:...)
{
	#if EBLOCK_DEBUG	
	decl String:buffer[192];
	
	VFormat(buffer, sizeof(buffer), format, 2);
	PrintToChatAll("%s", buffer);
	LogMessage("[READY] %s", buffer);
	#else
	//suppress "format" never used warning
	if(format[0])
		return;
	else
		return;
	#endif
}